﻿using System;

namespace CommonFramework.FixMath
{
    /// <summary>
    ///   <para>Representation of 3D vectors and points using integers.</para>
    /// </summary>
    [Serializable]
    public struct FVector3Int : IEquatable<FVector3Int>
    {
        public static readonly FVector3Int Zero = new FVector3Int(0, 0, 0);
        public static readonly FVector3Int One = new FVector3Int(1, 1, 1);
        public static readonly FVector3Int Up = new FVector3Int(0, 1, 0);
        public static readonly FVector3Int Down = new FVector3Int(0, -1, 0);
        public static readonly FVector3Int Left = new FVector3Int(-1, 0, 0);
        public static readonly FVector3Int Right = new FVector3Int(1, 0, 0);
        public static readonly FVector3Int Forward = new FVector3Int(0, 0, 1);
        public static readonly FVector3Int Back = new FVector3Int(0, 0, -1);

        private int _x;
        private int _y;
        private int _z;

        /// <summary>
        ///   <para>X component of the vector.</para>
        /// </summary>
        public int X
        {
            get => this._x;
            set => this._x = value;
        }
        /// <summary>
        ///   <para>Y component of the vector.</para>
        /// </summary>
        public int Y
        {
            get => this._y;
            set => this._y = value;
        }
        /// <summary>
        ///   <para>Z component of the vector.</para>
        /// </summary>
        public int Z
        {
            get => this._z;
            set => this._z = value;
        }

        public FVector3Int(int x, int y, int z)
        {
            this._x = x;
            this._y = y;
            this._z = z;
        }

        /// <summary>
        ///   <para>Set x, y and z components of an existing Vector3Int.</para>
        /// </summary>
        public void Set(int x, int y, int z)
        {
            this._x = x;
            this._y = y;
            this._z = z;
        }

        public int this[int index]
        {
            get
            {
                switch (index)
                {
                    case 0:
                    return this.X;
                    case 1:
                    return this.Y;
                    case 2:
                    return this.Z;
                    default:
                    throw new IndexOutOfRangeException(string.Format("Invalid Vector3Int index addressed: {0}!",
                        (object)index));
                }
            }
            set
            {
                switch (index)
                {
                    case 0:
                    this.X = value;
                    break;
                    case 1:
                    this.Y = value;
                    break;
                    case 2:
                    this.Z = value;
                    break;
                    default:
                    throw new IndexOutOfRangeException(
                        string.Format("Invalid Vector3Int index addressed: {0}!", (object)index));
                }
            }
        }

        /// <summary>
        ///   <para>Returns the length of this vector (Read Only).</para>
        /// </summary>
        public FFloat Magnitude 
            => FMathUtil.Sqrt(FFloat.FromLong((long)this.X * this.X + this.Y * this.Y + this.Z * this.Z));

        /// <summary>
        ///   <para>Returns the squared length of this vector (Read Only).</para>
        /// </summary>
        public int sqrMagnitude
        {
            get { return this.X * this.X + this.Y * this.Y + this.Z * this.Z; }
        }

        /// <summary>
        ///   <para>Returns the distance between a and b.</para>
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public static FFloat Distance(FVector3Int a, FVector3Int b)
        {
            return (a - b).Magnitude;
        }

        /// <summary>
        ///   <para>Returns a vector that is made from the smallest components of two vectors.</para>
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        public static FVector3Int Min(FVector3Int lhs, FVector3Int rhs)
        {
            return new FVector3Int(FMathUtil.Min(lhs.X, rhs.X), FMathUtil.Min(lhs.Y, rhs.Y), FMathUtil.Min(lhs.Z, rhs.Z));
        }

        /// <summary>
        ///   <para>Returns a vector that is made from the largest components of two vectors.</para>
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        public static FVector3Int Max(FVector3Int lhs, FVector3Int rhs)
        {
            return new FVector3Int(FMathUtil.Max(lhs.X, rhs.X), FMathUtil.Max(lhs.Y, rhs.Y), FMathUtil.Max(lhs.Z, rhs.Z));
        }

        /// <summary>
        ///   <para>Multiplies two vectors component-wise.</para>
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        public static FVector3Int Scale(FVector3Int a, FVector3Int b)
        {
            return new FVector3Int(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
        }

        /// <summary>
        ///   <para>Multiplies every component of this vector by the same component of scale.</para>
        /// </summary>
        /// <param name="scale"></param>
        public void Scale(FVector3Int scale)
        {
            this.X *= scale.X;
            this.Y *= scale.Y;
            this.Z *= scale.Z;
        }

        /// <summary>
        ///   <para>Clamps the Vector3Int to the bounds given by min and max.</para>
        /// </summary>
        /// <param name="min"></param>
        /// <param name="max"></param>
        public void Clamp(FVector3Int min, FVector3Int max)
        {
            this.X = FMathUtil.Max(min.X, this.X);
            this.X = FMathUtil.Min(max.X, this.X);
            this.Y = FMathUtil.Max(min.Y, this.Y);
            this.Y = FMathUtil.Min(max.Y, this.Y);
            this.Z = FMathUtil.Max(min.Z, this.Z);
            this.Z = FMathUtil.Min(max.Z, this.Z);
        }

        //public static explicit operator LVector2Int(FVector3Int v)
        //{
        //    return new LVector2Int(v.x, v.y);
        //}


        public static FVector3Int operator +(FVector3Int a, FVector3Int b)
        {
            return new FVector3Int(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
        }

        public static FVector3Int operator -(FVector3Int a, FVector3Int b)
        {
            return new FVector3Int(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
        }

        public static FVector3Int operator *(FVector3Int a, FVector3Int b)
        {
            return new FVector3Int(a.X * b.X, a.Y * b.Y, a.Z * b.Z);
        }

        public static FVector3Int operator *(FVector3Int a, int b)
        {
            return new FVector3Int(a.X * b, a.Y * b, a.Z * b);
        }

        public static bool operator ==(FVector3Int lhs, FVector3Int rhs)
        {
            return lhs.X == rhs.X && lhs.Y == rhs.Y && lhs.Z == rhs.Z;
        }

        public static bool operator !=(FVector3Int lhs, FVector3Int rhs)
        {
            return !(lhs == rhs);
        }

        /// <summary>
        ///   <para>Returns true if the objects are equal.</para>
        /// </summary>
        /// <param name="other"></param>
        public override bool Equals(object other)
        {
            if (!(other is FVector3Int))
                return false;
            return this.Equals((FVector3Int)other);
        }

        public bool Equals(FVector3Int other)
        {
            return this == other;
        }

        /// <summary>
        ///   <para>Gets the hash code for the Vector3Int.</para>
        /// </summary>
        /// <returns>
        ///   <para>The hash code of the Vector3Int.</para>
        /// </returns>
        public override int GetHashCode()
        {
            int hashCode1 = this.Y.GetHashCode();
            int hashCode2 = this.Z.GetHashCode();
            return this.X.GetHashCode() ^ hashCode1 << 4 ^ hashCode1 >> 28 ^ hashCode2 >> 4 ^ hashCode2 << 28;
        }

        /// <summary>
        ///   <para>Returns a nicely formatted string for this vector.</para>
        /// </summary>
        /// <param name="format"></param>
        public override string ToString()
        {
            return string.Format("({0}, {1}, {2})", (object)this.X, (object)this.Y, (object)this.Z);
        }

        /// <summary>
        ///   <para>Returns a nicely formatted string for this vector.</para>
        /// </summary>
        /// <param name="format"></param>
        public string ToString(string format)
        {
            return string.Format("({0}, {1}, {2})", (object)this.X.ToString(format),
                (object)this.Y.ToString(format), (object)this.Z.ToString(format));
        }
    }
}